// Copyright (c) 2023 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::structure::*;
use std::cell::{Ref, RefMut};

/// `CharacterDataMethod` trait, which corresponds to `Interface CharacterData`.
///
/// # Association
/// [Interface CharacterData](https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-FF21A306)
pub trait CharacterDataMethod {
    /// Return the length of the node's value.
    ///
    /// # Association
    /// [CharacterData::length](https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-7D61178C)
    ///
    /// # Example
    /// ```
    /// use ylong_xml::{CharacterDataMethod, Document, DocumentMethod};
    ///
    /// let document = Document::new();
    /// let comment = document.create_comment("Comment").unwrap();
    /// assert_eq!(comment.len(), 7);
    /// ```
    fn len(&self) -> usize;

    /// Check if the length of value is 0.
    ///
    /// # Example
    /// ```
    /// use ylong_xml::{CharacterDataMethod, Document, DocumentMethod};
    ///
    /// let document = Document::new();
    /// let comment = document.create_comment("").unwrap();
    /// assert_eq!(comment.is_empty(), true);
    /// let comment = document.create_comment("Comment").unwrap();
    /// assert_eq!(comment.is_empty(), false);
    /// ```
    fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Extract a range of data from the node. Returns `Err` if the `offset`
    /// or `count` is invalid.
    ///
    /// # Association
    /// [CharacterData::substringData](https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-6531BCCF)
    ///
    /// # Example
    /// ```
    /// use ylong_xml::{CharacterDataMethod, Document, DocumentMethod, DomException};
    ///
    /// let document = Document::new();
    /// let comment = document.create_comment("Comment").unwrap();
    /// assert_eq!(comment.substring_data(1, 3).ok(), Some(String::from("omm")));
    /// ```
    fn substring_data(&self, offset: usize, count: usize) -> Result<String, XmlError>;

    /// Append the string to the end of the character data of the node. Upon
    /// success, `data` provides access to the concatenation of data and the
    /// `&str` specified. Returns `Err` if the given `arg` is invalid.
    ///
    /// # Association
    /// [CharacterData::appendData](https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-32791A2F)
    ///
    /// # Example
    /// ```
    /// use ylong_xml::{CharacterDataMethod, Document, DocumentMethod, ValuedNodeMethod};
    ///
    /// let document = Document::new();
    /// let mut comment = document.create_comment("Comment").unwrap();
    /// assert!(comment.append_data("'s Value").is_ok());
    /// assert_eq!(comment.value(), String::from("Comment's Value"));
    /// ```
    fn append_data(&mut self, arg: &str) -> Result<(), XmlError>;

    /// Insert a string at the specified offset.
    ///
    /// # Association
    /// [Character::insertData](https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-3EDB695F)
    ///
    /// # Example
    /// ```
    /// use ylong_xml::{CharacterDataMethod, Document, DocumentMethod, ValuedNodeMethod};
    ///
    /// let document = Document::new();
    /// let mut comment = document.create_comment("Ct").unwrap();
    /// assert!(comment.insert_data(1, "ommen").is_ok());
    /// assert_eq!(comment.value(), String::from("Comment"));
    /// ```
    fn insert_data(&mut self, offset: usize, arg: &str) -> Result<(), XmlError>;

    /// Remove a substring from the node. Upon success, data and length reflect
    /// the change.
    ///
    /// # Association
    /// [CharacterData::deleteData](https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-7C603781)
    ///
    /// # Example
    /// ```
    /// use ylong_xml::{CharacterDataMethod, Document, DocumentMethod, ValuedNodeMethod};
    ///
    /// let document = Document::new();
    /// let mut comment = document.create_comment("Cabcdomment").unwrap();
    /// assert!(comment.delete_data(1, 4).is_ok());
    /// assert_eq!(comment.value(), String::from("Comment"));
    /// ```
    fn delete_data(&mut self, offset: usize, count: usize) -> Result<(), XmlError>;

    /// Replace the characters starting at the specified offset with the
    /// specified string.
    ///
    /// # Association
    /// [CharacterData::replaceData](https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-E5CBA7FB)
    ///
    /// # Example
    /// ```
    /// use ylong_xml::{CharacterDataMethod, Document, DocumentMethod, ValuedNodeMethod};
    ///
    /// let document = Document::new();
    /// let mut comment = document.create_comment("Cabcdet").unwrap();
    /// assert!(comment.replace_data(1, 5, "ommen").is_ok());
    /// assert_eq!(comment.value(), String::from("Comment"));
    /// ```
    fn replace_data(&mut self, offset: usize, count: usize, arg: &str) -> Result<(), XmlError>;
}

impl<T: CharacterDataBorrowMethod> CharacterDataMethod for T {
    fn len(&self) -> usize {
        self.borrow().len()
    }

    fn substring_data(&self, offset: usize, count: usize) -> Result<String, XmlError> {
        self.borrow_mut().substring_data(offset, count)
    }

    fn append_data(&mut self, arg: &str) -> Result<(), XmlError> {
        self.borrow_mut().append_data(arg)
    }

    fn insert_data(&mut self, offset: usize, arg: &str) -> Result<(), XmlError> {
        self.borrow_mut().insert_data(offset, arg)
    }

    fn delete_data(&mut self, offset: usize, count: usize) -> Result<(), XmlError> {
        self.borrow_mut().delete_data(offset, count)
    }

    fn replace_data(&mut self, offset: usize, count: usize, arg: &str) -> Result<(), XmlError> {
        self.borrow_mut().replace_data(offset, count, arg)
    }
}

/// `CharacterDataBorrowMethod` is used to reduce duplication of code.
pub(crate) trait CharacterDataBorrowMethod: NodeMethod {
    type Inner: CharacterDataInternalMethod;

    /// Get a reference of `CharacterData`'s inner type.
    fn borrow(&self) -> Ref<'_, Self::Inner>;

    /// Get a mutable reference of `CharacterData`'s inner type.
    fn borrow_mut(&self) -> RefMut<'_, Self::Inner>;
}

/// `CharacterDataInternalMethod` is used to reduce duplication of code.
pub(crate) trait CharacterDataInternalMethod {
    /// Get a reference of the inner data.
    fn get_data(&self) -> &String;

    /// Get a mutable reference of the inner data.
    fn get_data_mut(&mut self) -> &mut String;

    /// Get the length of the inner data.
    fn len(&self) -> usize {
        self.get_data().len()
    }

    /// Get a substring of the inner data with the given offset and count.
    fn substring_data(&self, offset: usize, count: usize) -> Result<String, XmlError> {
        let data = self.get_data();
        if offset.wrapping_add(count) <= data.len() {
            return Ok(data[offset..offset + count].to_string());
        }
        Err(DomException::IndexSizeErr.into())
    }

    /// Add extra content to the end of data.
    fn append_data(&mut self, arg: &str) -> Result<(), XmlError> {
        let data = self.get_data_mut();
        data.push_str(arg);
        Ok(())
    }

    /// Insert extra content at the specified location.
    fn insert_data(&mut self, offset: usize, arg: &str) -> Result<(), XmlError> {
        let data = self.get_data_mut();
        if offset <= data.len() {
            data.insert_str(offset, arg);
            return Ok(());
        }
        Err(DomException::IndexSizeErr.into())
    }

    /// Delete the content at the specified location.
    fn delete_data(&mut self, offset: usize, count: usize) -> Result<(), XmlError> {
        let data = self.get_data_mut();
        if offset.wrapping_add(count) <= data.len() {
            data.replace_range(offset..offset + count, "");
            return Ok(());
        }
        Err(DomException::IndexSizeErr.into())
    }

    /// Replace the content at the specified location.
    fn replace_data(&mut self, offset: usize, count: usize, arg: &str) -> Result<(), XmlError> {
        let data = self.get_data_mut();
        if offset.wrapping_add(count) <= data.len() {
            data.replace_range(offset..offset + count, arg);
            return Ok(());
        }
        Err(DomException::IndexSizeErr.into())
    }
}

#[cfg(test)]
mod ut_character_data {
    use crate::structure::*;

    /// UT test case for `CharacterData::len()`.
    ///
    /// # Brief
    /// 1. Create a `Comment` called `comment`.
    /// 2. Call `len()` method to get the length of `comment`'s inner data.
    /// 3. Check if the length is correct.
    #[test]
    fn ut_character_data_len() {
        let document = Document::new();
        let comment = document.create_comment("Comment").unwrap();
        assert_eq!(comment.len(), 7);
    }

    /// UT test case for `CharacterData::is_empty()`.
    ///
    /// # Brief
    /// 1. Create a `Comment` called `comment`.
    /// 2. Call `is_empty()` method to check if `comment`'s inner data is empty.
    #[test]
    fn ut_character_data_is_empty() {
        let document = Document::new();
        let comment = document.create_comment("").unwrap();
        assert!(comment.is_empty());

        let comment = document.create_comment("Comment").unwrap();
        assert!(!comment.is_empty());
    }

    /// UT test case for `CharacterData::substring_data()`.
    ///
    /// # Brief
    /// 1. Create a `Comment` called `comment`.
    /// 2. Call `substring_data()` method to get a substring of `comment`.
    /// 3. Check if the substring is correct.
    #[test]
    fn ut_character_data_substring_data() {
        let document = Document::new();
        let comment = document.create_comment("Comment").unwrap();
        let substring = comment.substring_data(1, 3);
        assert_eq!(substring, Ok(String::from("omm")));

        // Invalid offset (offset >= len).
        let substring = comment.substring_data(100, 1);
        assert_eq!(substring, Err(DomException::IndexSizeErr.into()));

        // Invalid count (count > len).
        let substring = comment.substring_data(1, 100);
        assert_eq!(substring, Err(DomException::IndexSizeErr.into()));

        // Invalid offset + count (offset < len, count <= len, offset + count > len).
        let substring = comment.substring_data(6, 4);
        assert_eq!(substring, Err(DomException::IndexSizeErr.into()));
    }

    /// UT test case for `CharacterData::append_data()`.
    ///
    /// # Brief
    /// 1. Create a `Comment` called `comment`.
    /// 2. Call `append_data()` method to append some extra content to `comment`.
    /// 3. Check if `comment`'s data is correct.
    #[test]
    fn ut_character_data_append_data() {
        let document = Document::new();
        let mut comment = document.create_comment("Comment").unwrap();
        assert!(comment.append_data("123").is_ok());
        assert_eq!(comment.value(), "Comment123");
    }

    /// UT test case for `CharacterData::insert_data()`.
    ///
    /// # Brief
    /// 1. Create a `Comment` called `comment`.
    /// 2. Call `insert_data()` method to add some extra content to `comment`.
    /// 3. Check if `comment`'s data is correct.
    #[test]
    fn ut_character_data_insert_data() {
        let document = Document::new();
        let mut comment = document.create_comment("Comment").unwrap();
        assert!(comment.insert_data(0, "New").is_ok());
        assert_eq!(comment.value(), "NewComment");

        // Invalid offset.
        assert_eq!(
            comment.insert_data(100, "New"),
            Err(DomException::IndexSizeErr.into())
        );
    }

    /// UT test case for `CharacterData::delete_data()`.
    ///
    /// # Brief
    /// 1. Create a `Comment` called `comment`.
    /// 2. Call `insert_data()` method to add some extra content to `comment`.
    /// 3. Check if `comment`'s data is correct.
    #[test]
    fn ut_character_data_delete_data() {
        let document = Document::new();
        let mut comment = document.create_comment("Comment").unwrap();
        assert!(comment.delete_data(1, 3).is_ok());
        assert_eq!(comment.value(), "Cent");

        // Invalid offset
        assert_eq!(
            comment.delete_data(100, 1),
            Err(DomException::IndexSizeErr.into())
        );

        // Invalid count
        assert_eq!(
            comment.delete_data(1, 100),
            Err(DomException::IndexSizeErr.into())
        );

        // Invalid offset + count
        assert_eq!(
            comment.delete_data(2, 3),
            Err(DomException::IndexSizeErr.into())
        );
    }

    /// UT test case for `CharacterData::replace_data()`.
    ///
    /// # Brief
    /// 1. Create a `Comment` called `comment`.
    /// 2. Call `insert_data()` method to add some extra content to `comment`.
    /// 3. Check if `comment`'s data is correct.
    #[test]
    fn ut_character_data_replace_data() {
        let document = Document::new();
        let mut comment = document.create_comment("Comment").unwrap();
        assert!(comment.replace_data(1, 4, "ou").is_ok());
        assert_eq!(comment.value(), "Count");

        // Invalid offset
        assert_eq!(
            comment.replace_data(100, 1, "abc"),
            Err(DomException::IndexSizeErr.into())
        );

        // Invalid count
        assert_eq!(
            comment.replace_data(1, 100, "abc"),
            Err(DomException::IndexSizeErr.into())
        );

        // Invalid offset + count
        assert_eq!(
            comment.replace_data(3, 4, "abc"),
            Err(DomException::IndexSizeErr.into())
        );
    }
}
